package Streaming;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.IOException;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

import javax.media.ConfigureCompleteEvent;
import javax.media.Controller;
import javax.media.ControllerEvent;
import javax.media.ControllerListener;
import javax.media.EndOfMediaEvent;
import javax.media.Format;
import javax.media.MediaLocator;
import javax.media.Player;
import javax.media.PlugInManager;
import javax.media.Processor;
import javax.media.ProcessorModel;
import javax.media.control.TrackControl;
import javax.media.format.AudioFormat;
import javax.media.protocol.ContentDescriptor;
import javax.media.protocol.DataSource;
import javax.media.rtp.RTPManager;
import javax.media.rtp.SendStream;
import javax.media.rtp.SessionAddress;

public class StreamingModule implements ControllerListener  {

	// generated Serial Version UID
	@SuppressWarnings("unused")
	private static final long serialVersionUID = -2971893319990240407L;

	private static final int SEND_PORT = 1337;

	// CONSTANT FORMATS
	private static final Format mpeg3 = new AudioFormat(AudioFormat.MPEGLAYER3);
	private static final Format mpeg = new AudioFormat(AudioFormat.MPEG);
	private static final Format linear = new AudioFormat(AudioFormat.LINEAR);

	// instances of JMF classes
	// private MediaLocator mediaLocator = null;
	// private DataSink dataSink = null;
	private Processor mediaProcessor = null;
	private ProcessorModel procModel = null;
	private RTPManager rtpManager = null;
	private MediaLocator medLoc = null;

	// own variables
	private SessionAddress localAdress = null;
	private SendStream sendStream;
	private Player player;

	private boolean realized = false;
	private boolean configured = false;
	private boolean alreadysending = false;

	private static final Format[] FORMATS = new Format[] { new AudioFormat(AudioFormat.MSADPCM) };
	
	private static final ContentDescriptor CONTENT_DESCRIPTOR = new ContentDescriptor(ContentDescriptor.RAW_RTP);

	private ArrayList<File> playlist;
	private List<String> receivers;
	private int startindex;
	
	private LinkedList<ActionListener> listeners;
	private String error;

	/**
	 * public constructor
	 */
	public StreamingModule(ArrayList<File> files, List<String> recs, int i) {
		this.playlist = files;
		this.receivers = recs;
		this.startindex = (i < 0) ? 0 : i;

		// call of a deprecated method
		// addMP3Support();
		
		localAdress = new SessionAddress();
		listeners = new LinkedList<ActionListener>();
	}

//	@Override
//	public void run() {
//		if (!alreadysending) {
//			transmit();
//		}
//	}

	/**
	 * streams the audio file selected by the index from the current playlist.
	 */
	
	public void transmit() {

		try {
			alreadysending = true;

			medLoc = new MediaLocator(playlist.get(startindex).toURI().toURL());

			// to hear what we stream
			player = Manager.createRealizedPlayer(medLoc);

			// set up data source (song)
			DataSource source = Manager.createDataSource(medLoc);

			source.connect();

			// // set up processor model
			procModel = new ProcessorModel(source, FORMATS, CONTENT_DESCRIPTOR);

			// using JMF to get a set up processor
			mediaProcessor = Manager.createRealizedProcessor(procModel);
			
			// used to create and realize a player on my own - see commented code below
//			mediaProcessor = Manager.createProcessor(medLoc);
			
			mediaProcessor.addControllerListener(this);
			
			// set up processor to convert data source to RTP packets
			// configure on my own
//			mediaProcessor = Manager.createProcessor(source);
//			mediaProcessor.configure();
//
//			while (!configured) {
//				try {
//					Thread.sleep(100);
//				} catch (InterruptedException i) {
//					// ignore
//				}
//			}
//
//			setTrackFormat(mediaProcessor);
//
//			mediaProcessor.setContentDescriptor(CONTENT_DESCRIPTOR);
//			mediaProcessor.setSource(source);
//
//			mediaProcessor.realize();
//
//			while (mediaProcessor.getState() != Controller.Realized) {
//				Thread.sleep(100);
//			}
//			System.out.println("Realized");

			// set up the RTP Manager
			rtpManager = RTPManager.newInstance();
			rtpManager.initialize(localAdress);

			SessionAddress destAddr;

			for (String recipient : receivers) {
				destAddr = new SessionAddress(InetAddress.getByName(recipient),
						SEND_PORT);
				rtpManager.addTarget(destAddr);
			}

			mediaProcessor.start();
			player.start();

			// using send Stream
			// here the main bug occurs, because of the faulty data source 
			// of the processor
			sendStream = rtpManager.createSendStream(source, 1);
			sendStream.start();
		} catch (Exception e) {
			error = e.getClass().toString();
			fireErrorEvent();
			stopPlaying();
		}
	}

	/**
	 * handle the needed events that are fired by the processor.
	 * An EndOfMediaEvent restarts the transmission with an increased
	 * index (next song).
	 */
	
	public void controllerUpdate(ControllerEvent evt) {
		if (evt instanceof ConfigureCompleteEvent) {
			configured = true;
		} else if (evt instanceof EndOfMediaEvent) {
			
			startindex++;

			if (startindex >= playlist.size()) {
				return;
			}
			transmit();
		}
	}

	/**
	 * tells the streaming module to stop transmitting
	 * and resets all member objects for a new transmission
	 */
	public void stopPlaying() {
		
		if (mediaProcessor != null
				&& mediaProcessor.getState() == Controller.Realized) {
			mediaProcessor.stop();
			mediaProcessor = null;
		}

		if (sendStream != null)
			try {
				sendStream.stop();
				sendStream = null;
			} catch (IOException e) {
				error = e.getClass().toString();
				fireErrorEvent();
			}

		if (player != null)
			player.stop();
		
		if (rtpManager != null) {
			rtpManager.dispose();
		}
		
		medLoc = null;
		procModel = null;
		alreadysending = false;
	}

//	/**
//	 *  restarts the transmission of the current audio file
//	 */
//	public synchronized void startPlaying() {
//		if (mediaProcessor != null
//				&& mediaProcessor.getState() == Controller.Realized)
//			mediaProcessor.start();
//		if (sendStream != null)
//			try {
//				sendStream.start();
//			} catch (IOException e) {
//				error = e.getClass().toString();
//				fireErrorEvent();
//			}
//	}
	
	public void addListener(ActionListener listener){
		listeners.add(listener);
	}

	// PRIVATE METHOD SECTION

	/**
	 * DEPRECATED
	 * this method should enable MP3 support but
	 * because SUN removed the general MP3 support in the JMF
	 * due to a copyright infringement this method is deprecated. 
	 */
	
	@Deprecated
	private void addMP3Support() {
		// add MP3 support
		PlugInManager.addPlugIn("com.sun.media.codec.audio.mp3.JavaDecoder",
				new Format[] { mpeg3, mpeg }, new Format[] { linear },
				PlugInManager.CODEC);
	}

	/**
	 * this method is used while the processor isn't realized.
	 * it checks the control tracks of the processor and writes 
	 * available formats to the command line.
	 * 
	 * This is one method I used to fix the programm in my hours of debugging...
	 * 
	 * @param p the method checks the settings of the given processor
	 */
	
	private void setTrackFormat(Processor p) {
		// Get the tracks from the processor
		TrackControl[] tracks = p.getTrackControls();

		// Do we have atleast one track?
		if (tracks == null || tracks.length < 1) {
			System.out.println("Couldn't find tracks in processor");
		}

		// Set the output content descriptor to RAW_RTP
		// This will limit the supported formats reported from
		// Track.getSupportedFormats to only valid RTP formats.
		ContentDescriptor cd = new ContentDescriptor(ContentDescriptor.RAW_RTP);
		p.setContentDescriptor(cd);

		Format supported[];
		Format chosen;
		boolean atLeastOneTrack = false;

		// Program the tracks.
		for (int i = 0; i < tracks.length; i++) {
			Format format = tracks[i].getFormat();
			System.out.println("current format is " + format.getEncoding());

			if (tracks[i].isEnabled()) {
				supported = tracks[i].getSupportedFormats();
				for (int n = 0; n < supported.length; n++)
					System.out.println("Supported format: " + supported[n]);

				// We've set the output content to the RAW_RTP.
				// So all the supported formats should work with RTP.
				// We'll just pick the first one.

				if (supported.length > 0) {
					chosen = supported[0]; // this is where I tried changing
											// formats
					tracks[i].setFormat(chosen);
					System.err.println("Track " + i
							+ " is set to transmit as: " + chosen);
					atLeastOneTrack = true;
				} else
					tracks[i].setEnabled(false);
			} else
				tracks[i].setEnabled(false);
		}
	}
	
	private void fireErrorEvent(){
		for(ActionListener l : listeners){
			l.actionPerformed(new ActionEvent(this,0, "Streaming module: "));
		}
	}
}
